package service

import (
	"errors"
	"fmt"
	"os"
	"strings"
	"time"

	"github.com/go-git/go-git/v5"
	"github.com/go-git/go-git/v5/plumbing/object"
	"github.com/go-git/go-git/v5/plumbing/transport/http"
	"github.com/studio-b12/gowebdav"
	"lucidity.note/app/common"
	"lucidity.note/app/common/bean"
	"lucidity.note/app/common/logger"
)

type RemoteSyncService struct{}

// 远程同步
func (*RemoteSyncService) SyncRemoteRepositoryService(notebookId string) ([]*bean.NoteTreeNode, error) {
	isConn := common.NetWorkStatus()
	if !isConn {
		return nil, errors.New("请联网之后在进行远程同步")
	}

	// 查询笔记本信息
	notebookService := &NotebookService{}
	notebook, err := notebookService.GetNotebookByIdService(notebookId)
	if nil != err {
		logger.FAULT.Println(fmt.Errorf("获取笔记本信息失败. => {%s}", err.Error()))
		return nil, errors.New("获取笔记本信息失败")
	}

	// 打开本地仓库
	localRepoPath := fmt.Sprintf("%s/%s", notebook.Path, notebook.Name)
	repository, err := git.PlainOpen(localRepoPath)
	if nil != err {
		logger.FAULT.Println(fmt.Errorf("打开本地仓库失败. => {%s}", err.Error()))
		return nil, errors.New("打开笔记本本地仓库失败")
	}

	if err := GitRepositoryPull(repository, "origin", notebook.Username, notebook.Password); nil != err {
		if !strings.EqualFold("already up-to-date", err.Error()) {
			logger.FAULT.Println(fmt.Errorf("拉取远程仓库失败. => {%s}", err.Error()))
			return nil, errors.New("拉取远程仓库失败")
		}
	}

	// 提交本地文件同步远端
	if err := GitRepositoryCommit(repository, ".", fmt.Sprintf("%s: 提交笔记文件.", time.Now().Format(time.RFC1123))); nil != err {
		logger.FAULT.Println(fmt.Errorf("拉取远程仓库失败. => {%s}", err.Error()))
		return nil, errors.New("提交笔记文件失败")
	}

	// 推送文件
	if err := repository.Push(&git.PushOptions{Auth: getAuth(notebook.Username, notebook.Password)}); nil != err {
		logger.FAULT.Println(fmt.Errorf("推送仓库到云端失败. => {%s}", err.Error()))
		return nil, errors.New("推送仓库到云端失败")
	}

	// 扫描本地文件,返回文件信息到前端页面
	notetreeService := &NoteTreeService{}
	if nodes, err := notetreeService.GetNoteTreeService(notebookId); nil != err {
		logger.FAULT.Println(fmt.Errorf("获取本地文件失败. => {%s}", err.Error()))
		return nil, errors.New("获取本地文件失败")
	} else {
		return nodes, nil
	}
}

// Git 远程仓库克隆
func GitRepositoryClone(localPath, username, password, syncaddr string) (*git.Repository, error) {
	repository, err := git.PlainClone(localPath, false, &git.CloneOptions{
		Auth: &http.BasicAuth{
			Username: username,
			Password: password,
		},
		URL:      syncaddr,
		Progress: os.Stdout,
	})
	return repository, err
}

// 拉取远程仓库
func GitRepositoryPull(repository *git.Repository, remoteName, username, password string) error {
	repository.Config()
	if workTree, err := repository.Worktree(); nil != err {
		return err
	} else {
		return workTree.Pull(&git.PullOptions{
			RemoteName: remoteName,
			Auth:       getAuth(username, password),
		})
	}
}

// 提交操作
func GitRepositoryCommit(repository *git.Repository, path, commitMsg string) error {
	workTree, err := repository.Worktree()
	if nil != err {
		return err
	}

	status, _ := workTree.Status() // map[string]*FileStatus
	for k, v := range status {
		if v.Worktree == git.Deleted {
			if _, err := workTree.Remove(k); nil != err {
				logger.FAULT.Println(fmt.Errorf("删除本地文件在Git中的索引失败. => {%s}", err.Error()))
				continue
			}
		} else {
			if _, err := workTree.Add(k); nil != err {
				logger.FAULT.Println(fmt.Errorf("新增本地Git仓库中的索引失败. => {%s}", err.Error()))
				return err
			}
		}
	}
	gitConfig := GetGitSyncConf()
	if commit, err := workTree.Commit(commitMsg, &git.CommitOptions{
		Author: &object.Signature{
			Name:  gitConfig.Username,
			Email: gitConfig.Email,
			When:  time.Now(),
		},
	}); nil != err {
		if _, err := repository.CommitObject(commit); nil != err {
			return err
		}
	}
	return nil
}

func getAuth(username, password string) *http.BasicAuth {
	return &http.BasicAuth{
		Username: username,
		Password: password,
	}
}

// 创建 Webdav Client 实例
func CreateWebdavClientInstance(username, password, url string) *gowebdav.Client {
	// https://dav.jianguoyun.com/dav/
	client := gowebdav.NewClient(url, username, password)
	return client
}

// 下载 WebDav 内容到本地:同步远程数据到本地
// localBasePath string            本地笔记本的基准路径
// readNode      string            需要读取的节点名称,第一次是笔记本的名称,之后在笔记本的基础叠加每次的节点
// client        *gowebdav.Client  WebDav Client
func DownloadToLocal(localBasePath string, readNode string, client *gowebdav.Client) {
	if remoteFileInfos, err := client.ReadDir(readNode); nil != err {
		logger.FAULT.Printf("读取远程文件节点[%s]失败: %s", readNode, err.Error())
		return
	} else {
		// 文件存在,本地同步操作开始
		for _, remoteFileInfo := range remoteFileInfos {
			// 在本地创建此文件夹,然后在继续递归需找下级
			currLocalBasePath := fmt.Sprintf("%s/%s/%s", localBasePath, readNode, remoteFileInfo.Name())

			exist, err := common.PathExists(currLocalBasePath)
			if nil != err {
				// 记录错误,开始下一个
				logger.FAULT.Printf("读取本地文件是否存在失败, 错误信息: => %s", err.Error())
				continue
			}

			// 判断是否为文件夹子
			if remoteFileInfo.IsDir() {
				if !exist {
					// 如果不存在则创建此文件夹
					if err := os.MkdirAll(currLocalBasePath, os.ModePerm); nil != err {
						logger.FAULT.Printf("创建本地文件夹[%s]失败, 错误信息: => %s", currLocalBasePath, err.Error())
						continue
					}
				}

				// 往下递归
				lastReadNode := fmt.Sprintf("%s/%s", readNode, remoteFileInfo.Name())
				DownloadToLocal(localBasePath, lastReadNode, client)
			} else {
				// 创建本地文件,并读取本地文件内容
				bytes, _ := client.Read(fmt.Sprintf("%s/%s", readNode, remoteFileInfo.Name()))
				if err := os.WriteFile(currLocalBasePath, bytes, os.ModePerm); nil != err {
					logger.FAULT.Printf("创建本地文件[%s]写入数据失败, 错误信息: => %s", currLocalBasePath, err.Error())
				}
			}
		}
	}
}
